Skip to content

fix: preserve reasoning_content for OpenAI-compatible reasoning models#604

Merged
yinwm merged 1 commit intosipeed:mainfrom
winterfx:fix/reasoning-content-missing
Feb 24, 2026
Merged

fix: preserve reasoning_content for OpenAI-compatible reasoning models#604
yinwm merged 1 commit intosipeed:mainfrom
winterfx:fix/reasoning-content-missing

Conversation

@winterfx
Copy link
Contributor

@winterfx winterfx commented Feb 21, 2026

Models like Moonshot kimi-k2.5 and DeepSeek-R1 return a reasoning_content field in assistant messages. When thinking is enabled, the API requires this field to be echoed back in subsequent requests. PicoClaw was silently dropping it, causing 400 errors on tool-call round-trips.

  • Add ReasoningContent to Message and LLMResponse types
  • Parse reasoning_content in openai_compat parseResponse()
  • Carry reasoning_content through assistant tool-call messages
  • Add unit test for reasoning_content parsing

Fixes #588

📝 Description

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

📚 Technical Context (Skip for Docs)

  • Reference URL:
  • **Reasoning:**reasoning_content is a standard field in OpenAI-compatible APIs for reasoning models. It was completely absent from the codebase — not parsed, not stored, not echoed back.

🧪 Test Environment

  • Hardware: Mac (Apple Silicon)
  • OS: macOS
  • Model/Provider: Moonshot kimi-k2.5
  • Channels: CLI

📸 Evidence (Optional)

Click to view Logs/Screenshots image after fixed image

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • I have updated the documentation accordingly.

Models like Moonshot kimi-k2.5 and DeepSeek-R1 return a
reasoning_content field in assistant messages. When thinking is enabled,
the API requires this field to be echoed back in subsequent requests.
PicoClaw was silently dropping it, causing 400 errors on tool-call
round-trips.

- Add ReasoningContent to Message and LLMResponse types
- Parse reasoning_content in openai_compat parseResponse()
- Carry reasoning_content through assistant tool-call messages
- Add unit test for reasoning_content parsing

Fixes sipeed#588
Copy link
Collaborator

@alexhoshina alexhoshina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Ready for @yinwm to merge.

Copy link
Collaborator

@Huaaudio Huaaudio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second. Preserving reasoning content is quite necessary for agent iterations.

Copy link

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix for a real problem. Reasoning models (DeepSeek-R1, kimi-k2.5, QwQ) return reasoning_content in assistant messages and require it echoed back on subsequent turns. Without this, tool-call round-trips fail with 400 errors because the API expects the field to be present.

What is correct:

  • ReasoningContent field added to both Message and LLMResponse in protocoltypes/types.go with omitempty so it is invisible for non-reasoning models.
  • Parsed from the API response in openai_compat/provider.go parseResponse().
  • Carried through the assistant message construction in loop.go when tool calls are present -- this is the critical path, since the reasoning content needs to survive the assistant->tool->assistant round-trip.
  • The test covers the important case: reasoning content + tool calls in the same response.

Edge cases to consider (non-blocking):

  • Streaming responses: If the codebase has a streaming path (SSE), reasoning_content may arrive as a separate delta field. This PR handles the non-streaming path correctly. If streaming is added later, it will need the same treatment.
  • Anthropic's thinking field: Anthropic uses a different field name (thinking blocks in the content array). This is correctly scoped to openai_compat only, so no conflict.
  • Cost/token implications: reasoning_content can be very large (thousands of tokens of chain-of-thought). Echoing it back on every turn means context grows faster. This is working-as-designed per the API spec, but worth noting for users of reasoning models with limited context windows.

The test is well-structured -- it validates that reasoning content, regular content, and tool calls are all parsed correctly from a single response. Approving.

@winterfx
Copy link
Contributor Author

Hi @yinwm, just a gentle reminder about the PR when you have a moment 🙂
Would really appreciate your review. Thanks!

@yinwm
Copy link
Collaborator

yinwm commented Feb 24, 2026

LGTM

@yinwm yinwm merged commit b6e965e into sipeed:main Feb 24, 2026
2 checks passed
@winterfx winterfx deleted the fix/reasoning-content-missing branch February 24, 2026 12:49
@Orgmar
Copy link
Contributor

Orgmar commented Feb 25, 2026

@winterfx Good find on reasoning_content being silently dropped. Getting 400 errors on tool-call round-trips with models like kimi-k2.5 must have been frustrating to debug. Carrying the field through the full message chain is the right fix.

We're putting together the PicoClaw Dev Group on Discord for contributors to chat and collaborate. If you'd like to join, send an email to support@sipeed.com with the subject [Join PicoClaw Dev Group] winterfx and we'll get you the invite link.

hyperwd pushed a commit to hyperwd/picoclaw that referenced this pull request Mar 5, 2026
…sing

fix: preserve reasoning_content for OpenAI-compatible reasoning models
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Moonshot kimi-k2.5 reasoning_content missing in tool calls leads to API Error 400

7 participants